package net.oschina.bilbodai.common.beanutil.property.mapping;

import com.google.common.base.Preconditions;
import net.oschina.bilbodai.common.beanutil.property.Property;
import net.oschina.bilbodai.common.beanutil.property.PropertyUtil;
import net.oschina.bilbodai.common.beanutil.property.annotation.MapIgnore;
import net.oschina.bilbodai.common.beanutil.property.annotation.Mapped;
import net.oschina.bilbodai.common.beanutil.property.annotation.MappedCollection;
import net.oschina.bilbodai.common.beanutil.reflection.FieldCallback;
import net.oschina.bilbodai.common.beanutil.reflection.Reflection;
import net.oschina.bilbodai.common.beanutil.util.CollectionUtils;
import net.oschina.bilbodai.common.beanutil.util.TypeUtil;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author daimao  Date: 2016/12/27 Time: 下午3:08
 * @version $Id$
 */
public final class PropertyMappings {

    public static PropertyMapping create(final Class<?> targetType) {
        return create(targetType, new HashSet<Class<?>>(), CollectionMappingMode.RECURSIVE);
    }

    public static PropertyMapping create(final Class<?> targetType, CollectionMappingMode globalCollectMapMode) {
        return create(targetType, new HashSet<Class<?>>(), globalCollectMapMode);
    }

    public static PropertyMapping create(final Class<?> targetType, final Set<Class<?>> recursiveCheck, final CollectionMappingMode globalCollectMapMode) {
        Preconditions.checkNotNull(targetType);
        Reflection reflection = new Reflection();
        recursiveCheck.add(targetType);

        final Map<String, MappingProperty> keyToValueMappingMap = new HashMap<>();
        reflection.scanFields(targetType, new FieldCallback() {
            @Override
            public boolean match(Field field, Class<?> fieldType) {
                return !field.isAnnotationPresent(MapIgnore.class);
            }

            @Override
            public void onFieldFound(Field field, Class<?> fieldType, String superPath, String path) {

                Mapped mapped = field.getAnnotation(Mapped.class);
                String id = mapped != null ? mapped.id() : field.getName();
                String key = path;
                if (id.length() > 0) {
                    if (mapped == null || mapped.additivity()) {
                        key = superPath + id;
                    } else {
                        key = id;
                    }
                }
                if (keyToValueMappingMap.containsKey(key)) {
                    throw new IllegalArgumentException("Duplicate mapping key found, key=" + key);
                }

                Property property = PropertyUtil.create(path, targetType);

                if (Collection.class.isAssignableFrom(fieldType)) {
                    List<Class<?>> genericTypes = TypeUtil.getGenericTypes(field);
                    if (!CollectionUtils.isEmpty(genericTypes)) {
                        Class<?> genericType = genericTypes.get(0);
                        if (!Reflection.isJdkType(genericType)) {
                            if (recursiveCheck.contains(genericType)) {
                                return;
                            }
                            recursiveCheck.add(genericType);
                            PropertyMapping propertyMapping = create(genericType, recursiveCheck, globalCollectMapMode);
                            Property idxProperty = null;
                            MappedCollection mappedCollection = field.getAnnotation(MappedCollection.class);
                            CollectionMappingMode collectMapMode = globalCollectMapMode;
                            if (mappedCollection != null) {
                                idxProperty = PropertyUtil.create(mappedCollection.keyPath(), genericType);
                                if (mappedCollection.mode() != CollectionMappingMode.NOT_SET) {
                                    collectMapMode = mappedCollection.mode();
                                }
                            }
                            if (collectMapMode.ge(CollectionMappingMode.AS_ONE)) {
                                keyToValueMappingMap.put(key, new CollectMappingProperty(property, propertyMapping, key, idxProperty, collectMapMode));
                            }
                            return;
                        }
                    }
                }

                keyToValueMappingMap.put(key, new SimpleMappingProperty(property));
            }
        });
        return new DefaultPropertyMapping(keyToValueMappingMap);
    }

}